{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.1 用大模型构建新人答疑机器人\n",
    "\n",
    "## 🚄 前言\n",
    "你在一家教育内容开发公司工作，随着新员工的持续加入，频繁的答疑需求导致了显著的时间与资源成本。你决定使用大模型技术来构建一个答疑机器人，提升答疑的准确率与效率。\n",
    "\n",
    "## 🍁 课程目标\n",
    "学完本课程后，你将能够：\n",
    "- 通过API调用通义千问\n",
    "- 学习大模型的工作原理\n",
    "- 了解大模型的局限性及其解决方案\n",
    "\n",
    "## 💻 1. 通过API调用通义千问\n",
    "\n",
    "体验大模型最直接的方式就是通过网页界面（如[通义千问](https://tongyi.aliyun.com/qianwen/)）与它对话。不过作为开发者，往往需要在自己的应用中集成大模型能力，你可以使用业界广泛采用的 OpenAI Python SDK 来调用通义千问大模型。你需要先在计算环境中安装必要的依赖。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 安装openai SDK\n",
    "! pip install -r requirements.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了调用通义千问，你需要前往阿里云大模型服务平台百炼，开通 [模型调用服务](https://bailian.console.aliyun.com/#/model-market) 并 [创建一个API Key](https://bailian.console.aliyun.com/?apiKey=1#/api-key)。\n",
    "\n",
    "> 如果页面顶部显示如下，则表示你尚未开通百炼的模型调用服务。开通服务后可以调用模型。\n",
    "> <img src=\"https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/5298748271/p856749.png\" width=\"600\">\n",
    "\n",
    "在使用 API 之前，你需要妥善处理 API Key 的安全问题。直接在代码中写入 API Key 是一个不好的习惯，因为这样很容易在分享代码时泄露密钥，并且在更换API Key后所有明文编码的API Key部分都需要修改。更安全、便捷的做法是将 API Key 存储在环境变量中。\n",
    "\n",
    "下面的代码会从配置文件中加载你的 API Key，并将其设置为环境变量。代码执行后会显示 API Key 的前五位字符（后面用星号代替），这样你可以确认配置是否正确，同时又不会暴露完整的密钥。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T08:58:03.667571Z",
     "iopub.status.busy": "2024-11-15T08:58:03.667184Z",
     "iopub.status.idle": "2024-11-15T08:59:44.060371Z",
     "shell.execute_reply": "2024-11-15T08:59:44.059650Z",
     "shell.execute_reply.started": "2024-11-15T08:58:03.667545Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你配置的 API Key 是：sk-76*****\n"
     ]
    }
   ],
   "source": [
    "# 加载百炼的 API Key 用于调用通义千问大模型\n",
    "import os\n",
    "from config.load_key import load_key\n",
    "load_key()\n",
    "print(f'''你配置的 API Key 是：{os.environ[\"DASHSCOPE_API_KEY\"][:5]+\"*\"*5}''')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "先来尝试一个简单的对话。下面的代码创建了一个名为“公司小蜜”的助手，它可以回答关于公司运营的问题。你可以使用“选择项目管理工具”这个常见问题作为示例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T08:59:46.180449Z",
     "iopub.status.busy": "2024-11-15T08:59:46.179984Z",
     "iopub.status.idle": "2024-11-15T08:59:59.498380Z",
     "shell.execute_reply": "2024-11-15T08:59:59.497544Z",
     "shell.execute_reply.started": "2024-11-15T08:59:46.180423Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "选择适合公司项目管理的工具需要根据团队的具体需求、规模、工作流程以及预算来决定。以下是一些被广泛使用的项目管理工具，您可以根据自身情况进行选择：\n",
      "\n",
      "1. **Trello**：适用于敏捷开发和小型团队，界面直观，操作简单。通过看板的形式帮助团队成员跟踪项目的进度。\n",
      "\n",
      "2. **Jira**：特别适合软件开发团队使用，支持敏捷开发方法论，功能强大，可以定制化程度高，但学习成本相对较高。\n",
      "\n",
      "3. **Asana**：适合各种规模的企业使用，提供了任务分配、时间线规划、进度跟踪等功能，界面友好，易于上手。\n",
      "\n",
      "4. **Microsoft Project**：大型企业常用的项目管理软件，功能全面，支持复杂的项目计划和资源调度，但价格相对较高。\n",
      "\n",
      "5. **Teambition（团队版）**：国内较为流行的团队协作工具，集成了任务管理、文件共享、日程安排等多功能于一体，适合中小型企业及创业团队使用。\n",
      "\n",
      "6. **钉钉/企业微信**：除了基本的消息沟通外，还提供了考勤、报销、审批等工作流管理功能，部分版本也包含了项目管理模块，适合追求高效协同办公的企业。\n",
      "\n",
      "建议您可以先试用几款不同的工具，了解它们的功能特点后，再结合您团队的实际需求做出选择。希望这些建议对您有所帮助！\n"
     ]
    }
   ],
   "source": [
    "from openai import OpenAI\n",
    "import os\n",
    "client = OpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    base_url=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    ")\n",
    "def get_qwen_response(prompt):\n",
    "    \n",
    "    response = client.chat.completions.create(\n",
    "        model=\"qwen-plus\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": \"你负责教育内容开发公司的答疑，你的名字叫公司小蜜，你要回答同事们的问题。\"},\n",
    "            {\"role\": \"user\", \"content\": prompt}\n",
    "        ]\n",
    "    )\n",
    "    return response.choices[0].message.content\n",
    "response = get_qwen_response(\"我们公司项目管理应该用什么工具\")\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 如果你想实现多轮对话（让大模型参考历史对话信息进行回复），可以参考[多轮对话](https://help.aliyun.com/zh/model-studio/user-guide/text-generation#865b38621dwin)。\n",
    "\n",
    "运行上面的代码后，你会注意到需要等待一段时间（约20秒）才能看到完整的回复。这是因为默认情况下，API 会等待模型生成完所有内容后才一次性返回结果。在实际应用中，这种等待可能会影响用户体验 —— 想象一下用户盯着一个空白界面等待20秒的场景！\n",
    "\n",
    "幸运的是，你可以使用\"流式输出\"（Streaming）来优化这个问题。使用流式输出时，模型会像人类打字一样，一边思考一边输出，让用户能够立即看到部分回复，大大提升交互体验。接下来，看看如何实现流式输出...\n",
    "> 💡 小贴士：流式输出只是改变了内容的展示方式，模型的思考过程和最终答案的质量都保持不变。你可以放心使用这个功能来优化你的应用体验。\n",
    "\n",
    "要实现流式输出，只需在之前的代码基础上添加 `stream=True` 参数，并调整输出方式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T09:00:04.752657Z",
     "iopub.status.busy": "2024-11-15T09:00:04.752276Z",
     "iopub.status.idle": "2024-11-15T09:00:18.769987Z",
     "shell.execute_reply": "2024-11-15T09:00:18.769374Z",
     "shell.execute_reply.started": "2024-11-15T09:00:04.752633Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "选择项目管理工具时，需要考虑团队的具体需求、项目的类型以及预算等因素。下面是一些常用的项目管理工具，您可以根据实际情况进行选择：\n",
      "\n",
      "1. **Trello**：适合小型团队或个人使用，界面直观，操作简单。通过卡片和列表的形式来展示任务进度，非常适合敏捷开发。\n",
      "\n",
      "2. **Jira**：专为软件开发团队设计，功能强大，支持敏捷开发流程。能够与多种开发工具集成，适用于需要详细跟踪bug和任务的项目。\n",
      "\n",
      "3. **Asana**：适合中大型企业，提供了丰富的任务管理和协作功能。可以创建任务、子任务，设置截止日期和提醒，还可以分配给不同的成员。\n",
      "\n",
      "4. **Microsoft Project**：适合大型复杂项目管理，提供详细的计划制定、资源分配等功能。对于需要高级别规划和报告的企业来说是个不错的选择。\n",
      "\n",
      "5. **Teambition**（腾讯旗下）：国内用户可能更熟悉这个平台，它提供了项目管理、文件共享、会议安排等多种协作功能，界面友好，易于上手。\n",
      "\n",
      "6. **钉钉**：除了基础的沟通功能外，还集成了考勤、报销、审批等企业管理功能，同时也支持项目管理模块，适合中国企业使用。\n",
      "\n",
      "建议先确定您的团队最关心的功能点是什么（比如任务分配、时间追踪、文档共享等），然后根据这些需求去试用几款工具，最终选择最适合您团队的那一款。希望这些建议对您有所帮助！"
     ]
    }
   ],
   "source": [
    "from openai import OpenAI\n",
    "import os\n",
    "\n",
    "client = OpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    base_url=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    ")\n",
    "def get_qwen_stream_response(prompt):\n",
    "    response = client.chat.completions.create(\n",
    "        model=\"qwen-plus\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": \"你负责教育内容开发公司的答疑，你的名字叫公司小蜜，你要回答同事们的问题。\"},\n",
    "            {\"role\": \"user\", \"content\": prompt}\n",
    "        ],\n",
    "        stream=True\n",
    "    )\n",
    "    for chunk in response:\n",
    "        yield chunk.choices[0].delta.content\n",
    "\n",
    "response = get_qwen_stream_response(\"我们公司项目管理应该用什么工具\")\n",
    "for chunk in response:\n",
    "    print(chunk, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过对通义千问模型的两次提问，你可能会发现一些有趣的现象：\n",
    "1. 即使问题完全相同，每次的回答都略有不同。这是大模型的特性之一，就像人类一样，它会用不同的方式表达相似的观点。\n",
    "2. 模型给出的建议集中在一些通用的项目管理工具上，比如 Jira、Trello 或 Asana。虽然大模型知识丰富，但它并不了解你公司的具体情况，比如已有的工具链、团队规模、预算限制等。\n",
    "> 公司使用的项目管理软件资料可以从docs/内容开发工程师岗位指导说明书.pdf文件中找到。\n",
    "\n",
    "这两个现象其实很有意思！为什么大模型会表现出这样的特点呢？要理解这一点，需要掀开大模型的“神秘面纱”，看看它是如何思考和工作的。别担心，你将通过简单直观的方式来理解这些概念。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 📚 2. 大模型是如何工作的\n",
    "近几十年来，人工智能经历了从基础算法到生成式AI的深刻演变。生成式AI通过学习大量数据可以创造出全新的内容，如文本、图像、音频和视频，这极大地推动了AI技术的广泛应用。常见的应用场景包括智能问答（如通义千问、GPT）、创意作画（如Stable Diffusion）以及代码生成（如通义灵码）等，涵盖了各个领域，让AI触手可及。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i3/O1CN01XhNFzh1bj3EybLhgk_!!6000000003500-0-tps-2090-1138.jpg\" width=\"600\">\n",
    "\n",
    "智能问答作为大模型最经典且广泛的应用之一，是我们探索大模型工作机制的最佳范例。接下来将介绍大模型在问答场景中的工作流程，帮助你更深入地理解其背后的技术原理。\n",
    "\n",
    "### 2.1. 大模型的问答工作流程\n",
    "下面以“ACP is a very”为输入文本向大模型发起一个提问，下图展示从发起提问到输出文本的完整流程。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i4/O1CN017JNKTk22p00d1nFKk_!!6000000007168-0-tps-2458-1194.jpg\" width=\"600\">\n",
    "\n",
    "大模型的问答工作流程有以下五个阶段：\n",
    "\n",
    "**第一阶段：输入文本分词化**\n",
    "\n",
    "分词（Token）是大模型处理文本的基本单元，通常是词语、词组或者符号。我们需要将“ACP is a very”这个句子分割成更小且具有独立语义的词语（Token），并且为每个Token分配一个ID。如有需要，你可以使用[Tokenizer API](https://help.aliyun.com/zh/dashscope/developer-reference/token-api?spm=5176.28197632.0.0.2130607dUIVd7Y&disableWebsiteRedirect=true)计算Token。\n",
    "\n",
    "<img src=\"https://gw.alicdn.com/imgextra/i1/O1CN019gAS3k1DrhwpIdHl6_!!6000000000270-0-tps-2414-546.jpg\" width=\"600\">\n",
    "\n",
    "**第二阶段：Token向量化**\n",
    "\n",
    "计算机只能理解数字，无法直接理解Token的含义。因此需要将Token进行数字化转换（即转化为向量），使其可以被计算机所理解。Token向量化会将每个Token转化为固定维度的向量。\n",
    "\n",
    "\n",
    "**第三阶段：大模型推理**\n",
    "\n",
    "大模型通过大量已有的训练数据来学习知识，当我们输入新内容，比如“ACP is a very”时，大模型会结合所学知识进行推测。它会计算所有可能Token的概率，得到候选Token的概率集合。最后，大模型通过计算选出一个Token作为下一个输出。\n",
    "\n",
    "这就解释了为什么当询问公司的项目管理工具时，模型无法提供内部工具的建议，这是因为其推测能力是基于已有的训练数据，对它未接触的知识无法给出准确的回答。因此，在需要答疑机器人回答私域知识时，需要针对性地解决这一问题，在本小节第3部分会进一步阐述。\n",
    "\n",
    "**第四阶段：输出Token**\n",
    "\n",
    "由于大模型会根据候选Token的概率进行随机挑选，这就会导致“即使问题完全相同，每次的回答都略有不同”。为了控制生成内容的随机性，目前普遍是通过temperature和top_p来调整的。\n",
    "\n",
    "例如，下图中大模型输出的第一个候选Token集合为“informative（50%）”、“fun（25%）”、“enlightening（20%）”、“boring（5%）”。通过调整temperature或top_p参数，将影响大模型在候选Token集合中的选择倾向，如选择概率最高的“informative”。你可以在本小节 2.2 中进一步了解这两个参数。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i3/O1CN01N93ZE81e6zAZA4TiK_!!6000000003823-0-tps-582-340.jpg\" width=\"300\">\n",
    "\n",
    "特别地，“informative”会被继续送入大模型，用于生成候选Token。这个过程被称为自回归，它会利用到输入文本和已生成文本的信息。大模型采用这种方法依次生成候选Token。\n",
    "\n",
    "**第五阶段：输出文本**\n",
    "\n",
    "循环第三阶段和第四阶段的过程，直到输出特殊Token（如<EOS>，end of sentence，即“句子结束”标记）或输出长度达到阈值，从而结束本次问答。大模型会将所有生成的内容输出。当然你可以使用大模型的流式输出能力，即预测一些Token立即进行返回。这个例子最终会输出“ACP is a very informative course.”。\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 影响大模型内容生成的随机性参数\n",
    "前面提到，大模型通过temperature和top_p参数控制生成内容的随机性，下面将阐述这两个参数的原理。\n",
    "\n",
    "#### 2.2.1 temperature：调整候选Token集合的概率分布\n",
    "\n",
    "正如前面提到的，大模型在输出next-token之前，会给出候选Token的概率。你可以把temperature想象成一种调节器，它其实是在调整候选Token的概率分布，给它们重新“评分”。通过调节这个参数，你可以灵活地控制生成文本的多样性和创造性。\n",
    "\n",
    "下图展示了不同temperature值对候选Token概率分布的影响。\n",
    "\n",
    ">图示的提示词为“在大模型ACP课程中，你可以学习”。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i4/O1CN019ZDA3e1k48OmAUT8R_!!6000000004629-0-tps-2526-1188.jpg\" width=\"600\">\n",
    "\n",
    "图示的低温度、中温度和高温度是依据通义千问plus模型的取值范围[0, 2)来划分。\n",
    "\n",
    "- 低温度（temperature=0.1）：大模型选择“RAG”的概率远高于其它候选Token，这会导致其输出相对单一。<br>\n",
    "- 中温度（temperature=0.7）：候选Token之间概率分布相对平缓，除“RAG”之外的候选Token出现的概率都在增加。<br>\n",
    "- 高温度（temperature=1.2）：原低概率的候选Token出现概率进一步提升，打乱原有的排序，进一步提高了大模型输出的随机性。在上图中，“画画”出现的概率反而最高。<br>\n",
    "\n",
    "通常，温度值越低，输出结果也相对固定；而温度越高，输出结果则更具多样性和创造性。因此，对于需要明确答案的场景，如生成代码或提取数据结构，建议将温度设置得较低。而在需要创意和多样化的场景中，如生成广告文案，适当提高温度会更合适。如果你没有特别需求，使用大模型的默认温度即可。\n",
    "\n",
    "下面用例子来看一下temperature的效果。示例代码会控制temperature的取值并对同一个问题提问10次。你可以观察不同temperature值下10次回答内容的波动情况。\n",
    "\n",
    ">temperature的示例代码跟接下来要讲解的top_p类似，因此在这里进行封装以便后面使用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-11-15T12:06:39.470715Z",
     "iopub.status.busy": "2024-11-15T12:06:39.470351Z",
     "iopub.status.idle": "2024-11-15T12:06:44.434877Z",
     "shell.execute_reply": "2024-11-15T12:06:44.434229Z",
     "shell.execute_reply.started": "2024-11-15T12:06:39.470689Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输出 1 : 骏马\n",
      "输出 2 : 驹子\n",
      "输出 3 : 骏马\n",
      "输出 4 : 骏马\n",
      "输出 5 : 驹子\n",
      "输出 6 : 骏马\n",
      "输出 7 : 驹子\n",
      "输出 8 : 骏马\n",
      "输出 9 : 骏马\n",
      "输出 10 : 骏马\n"
     ]
    }
   ],
   "source": [
    "from openai import OpenAI\n",
    "import os\n",
    "\n",
    "# 初始化OpenAI客户端\n",
    "client = OpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    base_url=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    ")\n",
    "\n",
    "def get_qwen_stream_response(prompt, temperature, top_p):\n",
    "    response = client.chat.completions.create(\n",
    "        model=\"qwen-plus\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": \"请把帮我续写内容，字数要求是4个汉字以内。\"},\n",
    "            {\"role\": \"user\", \"content\": prompt}\n",
    "        ],\n",
    "        temperature=temperature,\n",
    "        top_p=top_p,\n",
    "        stream=True\n",
    "    )\n",
    "    \n",
    "    for chunk in response:\n",
    "        yield chunk.choices[0].delta.content\n",
    "\n",
    "# temperature，top_p的默认值使用通义千问plus模型的默认值\n",
    "def print_qwen_stream_response(prompt, temperature=0.7, top_p=0.8, iterations=10):\n",
    "    for i in range(iterations):\n",
    "        print(f\"输出 {i + 1} : \", end=\"\")\n",
    "        response = get_qwen_stream_response(prompt, temperature, top_p)\n",
    "        output_content = ''\n",
    "        for chunk in response:\n",
    "            output_content += chunk\n",
    "        print(output_content)\n",
    "\n",
    "# 通义千问plus模型：temperature的取值范围是[0, 2)，默认值为0.7\n",
    "# 设置temperature=0\n",
    "print_qwen_stream_response(\"马也可以叫做\", temperature=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T12:06:45.417531Z",
     "iopub.status.busy": "2024-11-15T12:06:45.417146Z",
     "iopub.status.idle": "2024-11-15T12:06:50.629069Z",
     "shell.execute_reply": "2024-11-15T12:06:50.628320Z",
     "shell.execute_reply.started": "2024-11-15T12:06:45.417506Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输出 1 : 骏马。\n",
      "输出 2 : 骏马、 Mustang、赛马、走马。\n",
      "输出 3 : 骏马\n",
      "输出 4 : 骏马。\n",
      "输出 5 : 驷驹\n",
      "输出 6 : 驹子\n",
      "输出 7 : 赤兔\n",
      "输出 8 : 驷骥骏骝\n",
      "输出 9 : 赤兔\n",
      "输出 10 : 赤兔\n"
     ]
    }
   ],
   "source": [
    "# 设置temperature=1.9\n",
    "print_qwen_stream_response(\"马也可以叫做\", temperature=1.9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jupyter": {
     "outputs_hidden": true
    },
    "tags": []
   },
   "source": [
    "从实验中可以明显观察到，温度值越高，模型生成的内容就越丰富多样。\n",
    "\n",
    "#### 2.2.2 top_p：控制候选Token集合的采样范围\n",
    "\n",
    "top_p，实际上是一种筛选机制。你可以把它想象成一个过滤器，它从候选Token集合中挑选出符合特定条件的“小集合”。top_p按概率降序排列候选Token，选择累计概率达到某个阈值的候选Token作为新的候选集合，从而控制候选Token的范围。\n",
    "\n",
    "下图展示了不同top_p值对候选Token集合的采样效果。\n",
    ">图示的提示词为“在大模型ACP课程中，你可以学习”。\n",
    "\n",
    "<img src=\"https://img.alicdn.com/imgextra/i1/O1CN01w6MMfg1IGDbjS5xHE_!!6000000000865-0-tps-2512-1180.jpg\" width=\"600\">\n",
    "\n",
    "图示中蓝色部分表示累计概率达到top_p阈值（如0.5或0.9）的Token，它们组成新的候选集合；灰色部分则是未被选中的Token。当top_p=0.5时，模型优先选择最高概率的Token，即“RAG”；而当top_p=0.9时，模型会在“RAG”、“提示词”、“模型”这三个Token中随机选择一个生成输出。\n",
    "\n",
    "显然，top_p值越大，候选Token的范围越广，生成的内容更加多样化，适合创意写作和诗歌生成；而top_p值越小，候选Token的范围越小，输出更符合既定模式，适用于新闻初稿和代码生成等场景。如果top_p设置得非常小（如0.001），候选Token范围极其有限，模型会倾向于选择概率最高的Token，因此输出相对稳定，但内容的多样性和创意会降低。\n",
    "\n",
    "下面用例子来看一下top_p的效果。示例代码会控制top_p的取值并对同一个问题提问10次。你可以观察不同top_p值下10次回答内容的波动情况。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T12:06:52.894507Z",
     "iopub.status.busy": "2024-11-15T12:06:52.894139Z",
     "iopub.status.idle": "2024-11-15T12:06:58.171739Z",
     "shell.execute_reply": "2024-11-15T12:06:58.171137Z",
     "shell.execute_reply.started": "2024-11-15T12:06:52.894481Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输出 1 : 极智战神\n",
      "输出 2 : 极智战神\n",
      "输出 3 : 极智战神\n",
      "输出 4 : 极智游戏手机\n",
      "输出 5 : 极智战神\n",
      "输出 6 : 极智战神\n",
      "输出 7 : 极智战神\n",
      "输出 8 : 极智战神\n",
      "输出 9 : 极智战神\n",
      "输出 10 : 极智战神\n"
     ]
    }
   ],
   "source": [
    "# 通义千问plus模型：top_p取值范围为(0,1]，默认值为0.8。\n",
    "# 设置top_p=0.001\n",
    "print_qwen_stream_response(\"为一款智能游戏手机取名，可以是\", top_p=0.001)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T12:06:58.394593Z",
     "iopub.status.busy": "2024-11-15T12:06:58.394205Z",
     "iopub.status.idle": "2024-11-15T12:07:03.378361Z",
     "shell.execute_reply": "2024-11-15T12:07:03.377556Z",
     "shell.execute_reply.started": "2024-11-15T12:06:58.394567Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输出 1 : 极智游龙\n",
      "输出 2 : 极智战神\n",
      "输出 3 : 极智战神\n",
      "输出 4 : 极光战神\n",
      "输出 5 : 雷霆智玩\n",
      "输出 6 : 极客风暴\n",
      "输出 7 : 雷电幻影\n",
      "输出 8 : 雷电龙翔\n",
      "输出 9 : 闪电侠\n",
      "输出 10 : 雷神智游\n"
     ]
    }
   ],
   "source": [
    "# 设置top_p=0.9\n",
    "print_qwen_stream_response(\"为一款智能游戏手机取名，可以是\", top_p=0.9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据实验结果可以发现，top_p值越高，大模型的输出结果随机性越高。\n",
    "\n",
    "**是否需要同时调整temperature和top_p？**\n",
    "\n",
    "为了确保生成内容的可控性，建议不要同时调整top_p和temperature。同时调整可能导致输出结果不可预测和复杂。你可以优先调整其中一种参数，观察其对结果的影响，再逐步微调。\n",
    ">知识延展：top_k\n",
    "在通义千问系列模型中，参数top_k也有类似top_p的能力，可查阅[通义千问API文档](https://help.aliyun.com/zh/model-studio/developer-reference/use-qwen-by-calling-api?spm=a2c4g.11186623.help-menu-2400256.d_3_3_0.68332bdb2Afk2s&scm=20140722.H_2712576._.OR_help-V_1)。它是一种采样机制，从概率排名前k的Token中随机选择一个进行输出。一般来说，top_k越大，生成内容越多样化；top_k越小，内容则更固定。当top_k设置为1时，模型仅选择概率最高的Token，输出会更加稳定，但也会导致缺乏变化和创意。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ⚙️ 3. 让大模型能够回答私域知识问题\n",
    "为了让大模型能够回答私域知识问题，你可以选择以下两种方案：\n",
    "- **不改变模型**<br>\n",
    "    在提问时，直接传入私域知识相关的参考信息。\n",
    "- **改变模型**<br>\n",
    "    通过微调甚至训练一个新的模型，使其能够更好地理解和回答特定领域的问题。\n",
    "    \n",
    "考虑到微调和训练新模型的高成本，在私有知识问答场景中，你可以优先考虑通过提示词传递私域知识。这种方法更加简便且高效。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T09:29:26.248131Z",
     "iopub.status.busy": "2024-11-15T09:29:26.247769Z",
     "iopub.status.idle": "2024-11-15T09:29:35.595994Z",
     "shell.execute_reply": "2024-11-15T09:29:35.595379Z",
     "shell.execute_reply.started": "2024-11-15T09:29:26.248108Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好！你是软件一组的成员，根据公司的安排，你们目前仍然可以继续使用 **Jira** 进行项目管理。不过请注意，公司计划在2026年之前逐步将所有团队切换到 **Microsoft Project**。因此，建议你们在使用 Jira 的同时，也可以开始熟悉 Microsoft Project 的相关功能和操作，以便未来顺利过渡。\n",
      "\n",
      "如果你有任何关于 Jira 或 Microsoft Project 的具体问题，或者需要帮助进行过渡准备，随时可以联系我。希望这对你有帮助！"
     ]
    }
   ],
   "source": [
    "from llama_index.llms.openai_like import OpenAILike\n",
    "import os\n",
    "from llama_index.core.llms import ChatMessage\n",
    "\n",
    "# 所调用的模型\n",
    "llm = OpenAILike(\n",
    "    model=\"qwen-plus\",\n",
    "    api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    is_chat_model=True\n",
    ")\n",
    "\n",
    "# 用户问题\n",
    "user_question = \"我是软件一组的，请问项目管理应该用什么工具\"\n",
    "\n",
    "# 公司项目管理工具相关的知识\n",
    "knowledge = '''公司项目管理工具有两种选择：\n",
    "  1. **Jira**：对于软件开发团队来说，Jira 是一个非常强大的工具，支持敏捷开发方法，如Scrum和Kanban。它提供了丰富的功能，包括问题跟踪、时间跟踪等。\n",
    "\n",
    "  2. **Microsoft Project**：对于大型企业或复杂项目，Microsoft Project 提供了详细的计划制定、资源分配和成本控制等功能。它更适合那些需要严格控制项目时间和成本的场景。\n",
    "  \n",
    "  在一般情况下请使用Microsoft Project，公司购买了完整的许可证。软件研发一组、三组和四组正在使用Jira，计划于2026年之前逐步切换至Microsoft Project。\n",
    "'''\n",
    "\n",
    "# 构建消息内容\n",
    "messages = [\n",
    "    ChatMessage(role=\"system\",\n",
    "                content=\"你负责教育内容开发公司的答疑，你的名字叫公司小蜜，你要回答学员的问题。\" + knowledge),\n",
    "    ChatMessage(role=\"user\", content=user_question)\n",
    "]\n",
    "\n",
    "# 使用 OpenAILike 调用大模型，并流式返回结果\n",
    "response = llm.stream_chat(messages)\n",
    "for r in response:\n",
    "    print(r.delta, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在提问时传入参考信息后，大模型就能回答私有知识问题了。然而，这种方法的缺点也很明显：提示词长度有限，当私有数据量过大时，传入所有背景信息可能导致提示词过长，从而影响模型的处理效率或达到长度上限。\n",
    "\n",
    "为了解决这个问题，你可以在用户提问时，自动检索相关的私有知识，并将检索到的文档片段与用户输入合并后传给大模型，从而生成最终的答案。这样可以避免在提示词中直接传入大量背景信息。这种实现方案，也被称为检索增强式生成（RAG，Retrieval-Augmented Generation）。\n",
    "\n",
    "构建一个 RAG 应用通常会分为两个阶段：\n",
    "\n",
    "### 3.1 建立索引阶段\n",
    "<img src=\"https://gw.alicdn.com/imgextra/i2/O1CN010zLf411zVoZQ9cWsI_!!6000000006720-2-tps-1592-503.png\" width=\"600\">\n",
    "\n",
    "建立索引是为了将私有知识文档或片段转换为可以高效检索的形式，通过将文件内容分割并转化为多维向量（使用专用 Embedding 模型），并结合向量存储保留文本的语义信息，方便进行相似度计算。向量化使得模型能够高效检索和匹配相关内容，特别是在处理大规模知识库时，显著提高了查询的准确性和响应速度。\n",
    "\n",
    "这些向量经过 Embedding 模型处理后不仅很好地捕捉文本内容的语义信息，而且由于语义已经向量化，标准化，便于之后与检索语义向量进行相关度计算。\n",
    "\n",
    "### 3.2 检索与生成阶段\n",
    "<img src=\"https://img.alicdn.com/imgextra/i1/O1CN01vbkBXC1HQ0SBrC1Ii_!!6000000000751-2-tps-1776-639.png\" width=\"600\">\n",
    "\n",
    "检索生成是根据用户的提问，从索引中检索相关的文档片段，这些片段会与提问一起输入到大模型生成最终的回答。这样大模型就能够回答私有知识问题了。\n",
    "\n",
    "总的来说，基于RAG 结构的应用，既避免了将整个参考文档作为背景信息输入而导致的各种问题，又通过检索提取出了与问题最相关的部分，从而提高了大模型输出的准确性与相关性。\n",
    "\n",
    "## 📝 4. 本节小结\n",
    "在本节课程中，我们学习了以下内容：\n",
    "\n",
    "- **如何使用大模型 API**<br>\n",
    "    通过实际的代码示例，了解如何使用 API 调用大模型，体验其在问答任务中的能力。\n",
    "- **初步了解大模型的工作原理**<br>\n",
    "    了解大模型如何理解问题，并生成响应，同时探讨了大模型的随机性和知识范围的限制，以及如何弥补这些不足。\n",
    "    \n",
    "除了本节课程中的示例展示的任务之外，你还可以让大模型完成更多类型的任务，如内容生成、结构化信息提取、文本分类、情感分析等。同时在你的大模型应用中引入 RAG 方案能够扩展大模型所能处理的知识范围，下一小节我们将介绍创建RAG应用的方法。\n",
    "\n",
    "### 扩展阅读\n",
    "- 在学习本课程的时候，如果你想了解更多相关概念和原理，可以尝试让大模型进一步展开说明或给出学习建议：\n",
    "> 通义千问支持开启 enable_search 参数，这个参数可以让大模型在生成回答时利用互联网搜索结果来丰富其回复内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-11-15T09:37:23.667987Z",
     "iopub.status.busy": "2024-11-15T09:37:23.667598Z",
     "iopub.status.idle": "2024-11-15T09:37:52.407154Z",
     "shell.execute_reply": "2024-11-15T09:37:52.406568Z",
     "shell.execute_reply.started": "2024-11-15T09:37:23.667960Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当然可以！让我们用一个简单的例子来解释大模型中的注意力机制。\n",
      "\n",
      "假设你在读一本书，这本书有很多页。每一页上都有很多内容，但你不可能同时关注所有内容。所以你会选择性地关注某些部分，忽略其他部分。这就是注意力机制的基本思想。\n",
      "\n",
      "在大模型中，比如Transformer模型，注意力机制帮助模型在处理长文本时，能够“关注”到最重要的部分，而不是平等地对待每一个词。\n",
      "\n",
      "### 具体步骤\n",
      "\n",
      "1. **输入序列**：假设我们有一个句子：“猫在椅子上睡觉。”这个句子会被分成一个个单词（或更小的单元），形成一个序列：[猫, 在, 椅子, 上, 睡觉]。\n",
      "\n",
      "2. **生成查询、键和值**：对于每个单词，模型会生成三个向量：查询向量（Query）、键向量（Key）和值向量（Value）。这些向量是通过线性变换得到的。\n",
      "\n",
      "   - 查询向量（Query）：表示当前单词的关注点。\n",
      "   - 键向量（Key）：表示其他单词的特征。\n",
      "   - 值向量（Value）：表示其他单词的实际内容。\n",
      "\n",
      "3. **计算注意力分数**：对于每个单词，模型会计算它与其他所有单词的相似度（或相关性）。这个相似度通常通过点积（即向量的内积）来计算。例如，对于单词“猫”，模型会计算“猫”与“猫”、“在”、“椅子”、“上”、“睡觉”的相似度。\n",
      "\n",
      "4. **应用softmax函数**：为了确保所有注意力分数加起来等于1，模型会对这些相似度分数应用softmax函数。这样，每个单词都会有一个归一化的注意力权重。\n",
      "\n",
      "5. **加权求和**：最后，模型会根据这些注意力权重，对所有单词的值向量进行加权求和。这个加权求和的结果就是该单词的注意力输出。\n",
      "\n",
      "### 举例说明\n",
      "\n",
      "假设我们关注单词“猫”，模型会计算“猫”与每个单词的相似度：\n",
      "\n",
      "- “猫”与“猫”的相似度：0.9\n",
      "- “猫”与“在”的相似度：0.1\n",
      "- “猫”与“椅子”的相似度：0.2\n",
      "- “猫”与“上”的相似度：0.1\n",
      "- “猫”与“睡觉”的相似度：0.3\n",
      "\n",
      "经过softmax函数后，这些相似度变成了注意力权重：\n",
      "\n",
      "- “猫”的权重：0.6\n",
      "- “在”的权重：0.07\n",
      "- “椅子”的权重：0.14\n",
      "- “上”的权重：0.07\n",
      "- “睡觉”的权重：0.12\n",
      "\n",
      "然后，模型会根据这些权重，对每个单词的值向量进行加权求和，得到“猫”的注意力输出。\n",
      "\n",
      "### 总结\n",
      "\n",
      "注意力机制的核心思想是让模型在处理信息时，能够“关注”到最重要的部分，而不仅仅是平等地对待每一个词。通过这种方式，模型能够更好地理解和处理长文本，提高任务的性能。"
     ]
    }
   ],
   "source": [
    "from openai import OpenAI\n",
    "import os\n",
    "\n",
    "client = OpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    base_url=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    ")\n",
    "def get_qwen_stream_response(prompt):\n",
    "    response = client.chat.completions.create(\n",
    "        model=\"qwen-plus\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": \"你是一个学习规划助手。\"},\n",
    "            {\"role\": \"user\", \"content\": prompt}\n",
    "        ],\n",
    "        stream=True,\n",
    "        # 百炼上提供的通义千问 API 服务支持在生成内容时，根据需要进行互联网检索\n",
    "        extra_body={\"enable_search\": True}\n",
    "    )\n",
    "    for chunk in response:\n",
    "        yield chunk.choices[0].delta.content\n",
    "\n",
    "response = get_qwen_stream_response(\"请通俗易懂的解释大模型中的注意力机制的工作原理\")\n",
    "for chunk in response:\n",
    "    print(chunk, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 如果你对多模态大模型比较感兴趣，如最新的图片生成、视频生成、音乐生成等，可以前往[通义万相](https://tongyi.aliyun.com/wanxiang/)注册账号并免费体验。\n",
    "\n",
    "## 🔥 课后小测验\n",
    "【单选题】 2.1.1. 以下代码片段的作用是什么？\n",
    "```python\n",
    "os.environ[\"OPENAI_API_KEY\"] = \"your-api-key-here\"\n",
    "```\n",
    "A. 从磁盘加载API密钥。<br>\n",
    "B. 将API密钥存储到内存中。<br>\n",
    "C. 将API密钥设置为环境变量。<br>\n",
    "D. 创建一个新的 API 密钥。<br>\n",
    "参考答案：C<br>\n",
    "\n",
    "【案例分析题】2.1.2 小明正在使用通义千问开发一个写作助手应用。根据本节课程学习的大模型工作原理和参数调节知识，请分析以下场景并给出解决方案：\n",
    "场景A：每次让模型写一篇关于“人工智能发展”的文章时，生成的内容都非常相似，缺乏创意性。\n",
    "场景B：让模型写一份技术文档时，生成的内容经常偏离主题，加入了一些不相关的内容。\n",
    "请问：\n",
    "1. 基于本节课程中学习的大模型工作流程，这两个场景的问题产生的原因可能是什么？\n",
    "2. 应该如何调整 temperature 或 top_p 参数来解决这些问题？\n",
    "\n",
    "参考答案：\n",
    "场景A：\n",
    "原因：temperature 值设置过低，导致模型总是倾向于选择概率最高的 token，使生成内容缺乏多样性。\n",
    "解决方案：适当提高 temperature 值(如从0.3提高到0.7-0.9)，让模型在生成时有更多创意空间。\n",
    "场景B：\n",
    "原因：temperature 值可能设置过高，或 top_p 值过大，使模型可以选择概率较低的 token，导致内容偏离主题。\n",
    "解决方案：降低 temperature 值（如从1.2降到0.5-0.7），或将 top_p 设置在0.7-0.9之间，以保持内容的相关性和专业性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ✅评价反馈\n",
    "欢迎你参与[阿里云大模型ACP课程问卷](https://survey.aliyun.com/apps/zhiliao/Mo5O9vuie) 反馈学习体验和课程评价。\n",
    "你的批评和鼓励都是我们前进的动力！"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "learnacp",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
